Skip to content

Use CRL by default instead of OCSP on android #179

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

stormshield-gt
Copy link
Contributor

Hi, I have a certificate signed by let's encrypt, which only has a CRL extension but not an OCSP one, as for the let's encrypt new policy.

On the rustls-platform-verifier documentation on Android, from my understanding, it says that if there is not a stapled OCSP response, an OCSP extension or a CRL extension must be present.

But I've the following error:

WARN rustls_platform_verifier::verification::android: certificate was revoked: java.security.cert.CertPathValidatorException: Certificate does not specify OCSP responder    
ERROR rustls_platform_verifier::verification::android: failed to verify TLS certificate: invalid peer certificate: Revoked 

I use the latest version of the crate, 0.6.0.

This MR propose to enable CRL by default on Android

@ctz
Copy link
Member

ctz commented Jun 2, 2025

I guess my question on this is: reading the documentation it seems quite clear that CRLs are the fallback from OCSP, but the observed behaviour contradicts that. The described behaviour seems acceptable, so can we work out a way to have that behaviour rather whatever is happening here?

If the "fallback" thing doesn't actually work as described, maybe we can try two verifications with NO_FALLBACK and do the fallback ourselves?

@iliabylich
Copy link

I have exactly the same issue with my server that uses certificate from Let's Encrypt (certificate is returned by the server but then it gets immediately rejected with an error Certificate does not specify OCSP responder, which is true, openssl returns OCSP response: no response sent).

The patches fixes it for me. Thanks @stormshield-gt !

Copy link

@phatblat phatblat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the exact same change we made to the CertificateVerifier to work around these errors we saw in our apps.

@djc djc requested a review from complexspaces June 12, 2025 11:48
@djc
Copy link
Member

djc commented Jun 12, 2025

@complexspaces would be awesome if you could take a look at this and comment with your thoughts!

@andrew-signal
Copy link

andrew-signal commented Jun 13, 2025

I would actually strongly recommend against merging this PR in its current form, because it actually disables certificate revocation checking in practice in a few cases, and people who are using this library are likely to actively think they are getting that protection when they are not.

First, by setting the PKIXRevocationChecker.Option.NO_FALLBACK in combination with the PKIXRevocationChecker.Option.PREFER_CRLS option, you actually indicate that if the CRL does not indicate a revocation, you do not want to check the OSCP at all. The combination of these two options maps in the internals of the RevocationChecker to a ONLY_CRLS mode. In practice, this should not cause an issue, because they should be fed from the same source of truth, but it's worth calling out, and important for understanding the next more severe problem.

The second problem is that in practice, the CRL check is almost always failing in practice, so in combination with the PKIXRevocationChecker.Option.SOFT_FAIL option and the above issue, you're not actually getting any revocation coverage. The problem here is that since roughly Android M, Android has blocked insecure HTTP traffic by default, and by convention the CRLs are hosted via HTTP, not HTTPS. So, when the RevocationChecker tries to fetch the CRL, it fails with a java.security.cert.CertPathValidatorException: Unable to determine revocation status due to network error, which traces back to a java.io.IOException: Cleartext HTTP traffic to <RCL domain> not permitted.

If you unblock HTTP traffic for the CRL fetch, the current configuration works fine, and actually performs a revocation check as expected. This fix only patches over that the revocation check is actually silently failing completely.

I hope this is helpful!

  • Andrew

Comment on lines +334 to +335
PKIXRevocationChecker.Option.PREFER_CRLS,
PKIXRevocationChecker.Option.NO_FALLBACK
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As some immediate feedback, nonwithstanding the newly posted information about Android's internal behavior, this part would need to be gated behind if (!BuildConfig.TEST) in order to support our tests that validate stapled OCSP data.

Despite OCSP going on its way out I don't think the tests are removable yet because its stable-ish behavior and I don't see a reason to completely drop them yet.

@complexspaces
Copy link
Collaborator

@andrew-signal Thank you very much for the feedback here, I wouldn't have guessed that was the current platform behavior with any likelihood.

The fact that the CRL fetches are blocked by default is problematic for a library, but we might be able to solve the issue. According to the Android docs manifests from libraries will be merged (with lowest priority) into the end application's. Given that, we might be able to modify our manifest to include the allowance and have it automatically pulled into app builds. I'm not really a Gradle expert, so I'd appreciate feedback if you see a reason this wouldn't work:

<application
    android:usesCleartextTraffic="true"
/>

... you actually indicate that if the CRL does not indicate a revocation, you do not want to check the OSCP at all.

IIUC the situation correctly, I think that this is OK behavior to keep. Since OCSP and soft failing is a bit of a nothingburger, I don't think trying to fetch an OCSP revocation if a CRL (from a platform trusted CA, too) would have a security benefit and might just hurt performance.

Can I ask how you ran into this issue originally though? On the surface it sounds like Signal or another Android app you work with run into this and workaround it by adding usesCleartextTraffic="true" to the app manifests?

@andrew-signal
Copy link

andrew-signal commented Jun 15, 2025

The main concern with that solution is that Android's HTTP allow/block setting is on a per-process basis, so including that manifest flag will allow HTTP traffic to any domain for the entire app for any app that pulls in this library. To a certain extent, given the Android security model here, and how the CRL system works, that's somewhat inevitable. Consumers who don't like this behavior would have the ability to change it back. Still, that may upset some consumers, given the security focus here. The alternatives I see are:

Alternative A) To go ahead and create a custom network security policy only whitelisting the CRL domains for the root CAs, but that would be a maintenance nightmare.
Alternative B) To mirror what the major web browsers are doing, and use something like CRLite to manage CRLs. This is not ideal, because this is not the "platform" behavior, but also the default platform behavior on Android is to ignore CRL revocation altogether, so.

It's also worth noting that even these alternatives are only partial solutions, and would not avoid the problem completely - users are still free to add CAs to their trust stores, and in that case, the CRLs for those CAs would not be included in the network security policy whitelist or the CRLite, so you're back at square one re: arbitrary HTTP to arbitrary network endpoints.

I think in an ideal world, Android's built-in system libraries that are effectively upstream of this project would include something like CRLite for us to depend on to handle this properly. If this is the way the ecosystem is moving, it'd be great if Android's libraries handled it well for us, and so all applications could benefit. However, that seems like a non-starter in practice if for no other reason than that billions of Android devices are already deployed without such support built-in.

I don't really have anything to recommend in this situation right now. We ran into this issue as we are testing using rustls-platform-verifier as a dependency for more of our network stack, and ran into issues with the new Let's Encrypt change on Android like everyone else, so we wanted to run it down fully to find the most secure way to handle this problem. At this stage, we're still evaluating solutions. We have not deployed any changes or workarounds yet, because we don't actually use this on Android in production yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants